package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.product.service.TestService;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class TestServiceImpl implements TestService {

    // 引入操作redis的客户端工具类
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public String readLock() {
        // 初始化读写锁：读写锁的key要一致 要是一把锁
        // 参数中传递的是锁的名称
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("athome");

        // 获取读锁
        RLock rLock = readWriteLock.readLock();

        // 加10s锁
        rLock.lock(10, TimeUnit.SECONDS);

        // 读取缓存中的数据(缓存中先写一个msg)
        String msg = redisTemplate.opsForValue().get("msg");

        // 解锁
        //rLock.unlock();

        // 返回读取的内容
        return msg;
    }

    @Override
    public String writeLock() {
        // 初始化读写锁：读写锁的key要一致 要是一把锁
        // 参数中传递的是锁的名称
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("athome");

        // 获取读锁
        RLock rLock = readWriteLock.writeLock();

        // 加10s锁
        rLock.lock(10, TimeUnit.SECONDS);

        // 往缓存写入数据(key为msg)
        redisTemplate.opsForValue().set("msg", UUID.randomUUID().toString());

        return "写入完成！！！";
    }

    // 分布式锁：使用Redisson
    @Override
    public void testLock() {
        // 获取锁 ！！！
        String skuId = "39";// 访问 skuId 为 39 号的商品
        String lockKey = "lock:" + skuId; // 锁住的是每个商品的数据
        RLock lock = redissonClient.getLock(lockKey);

        // 加锁 ！！！
        lock.lock();

        // 获取缓存中对应 num 的值
        String value = redisTemplate.opsForValue().get("num");

        // 判断能否得到 num 的值
        if (StringUtils.isEmpty(value)) {
            return;
        }

        // 将 value 转成 数值型
        int num = Integer.parseInt(value);

        // 将 redis 中 num +1
        redisTemplate.opsForValue().set("num", String.valueOf(++num));

        // 解锁 ！！！
        lock.unlock();
    }


    /**
     * redis分布式锁四：优化之LUA脚本保证删除的原子性
     */
    /*@Override
    public void testLock() {
        // 1 利用 setnx 来加锁 setnx lock athome
        // 设置一个 UUID 防止误删除
        String uuid = UUID.randomUUID().toString();

        // 结合商品详情设置一个新的key
        String skuId = "39";// 访问 skuId 为 39 号的商品
        String lockKey = "lock:" + skuId; // 锁住的是每个商品的数据
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 2, TimeUnit.SECONDS);

        // 2 如果返回 true 上锁成功
        if (lock) {

            // 获取缓存中对应 num 的值

            String value = redisTemplate.opsForValue().get("num");
            // 判断能否得到 num 的值
            if (StringUtils.isEmpty(value)) {
                return;
            }

            // 将值转成 数值型
            int num = Integer.parseInt(value);

            // 把 redis 中 num +1
            redisTemplate.opsForValue().set("num", String.valueOf(++num));

            // !!!删除锁之前判断 代码执行的 uuid 与 缓存中的 uuid 是否一致
            // uuid 一致 完成业务后 将锁删除!!!
//            if (uuid.equals(redisTemplate.opsForValue().get("lock"))){
//                // 线程一：执行到判断，返回结果是true，还没有执行 delete，lock刚好过期时间已到。也就是说还差delete没执行。
//                // 线程二：进来了，重新设了 uuid 和 缓存的值！
//                // 此时，cpu 又开始执行线程一，线程一就会将线程二的锁给删除！！！
//                // 这样删除就不具备原子性！！！
//                redisTemplate.delete("lock");
//            }
            // 使用 Lua 脚本 删除锁

            // 定义 Lua 脚本：这个脚本只在客户端传入的值和键的口令串相匹配时，才对键进行删除。
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

            // 将 Lua 脚本 放入 defaultRedisScript
            DefaultRedisScript defaultRedisScript = new DefaultRedisScript();
            defaultRedisScript.setScriptText(script);

            // 设置一个返回值
            // 因为删除判断的时候，返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型，那么返回字符串与0 会有发生错误。
            defaultRedisScript.setResultType(Long.class);

            // 执行 Lua 脚本
            // execute(RedisScript<T> script, List<K> keys, Object... args)
            // 第一个要是script 脚本 ，第二个需要判断的key，第三个就是key所对应的值
            redisTemplate.execute(defaultRedisScript, Arrays.asList(lockKey), uuid);

        } else { // 若当前线程加锁了，之后的线程再进来程序，也会执行加锁的代码，但是会返回false(当前线程已经加锁了)
            try {
                // 4 等待之前的线程执行完
                Thread.sleep(1000);
                // 5 每隔1秒钟回调一次，再次尝试获取锁
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }*/

    /**
     * redis分布式锁三：优化之UUID防误删
     * 问题场景：如果业务逻辑的执行时间是7s。执行流程如下
     * index1业务逻辑没执行完，3秒后锁被自动释放。
     * index2获取到锁，执行业务逻辑，3秒后锁被自动释放。
     * index3获取到锁，执行业务逻辑
     * index1业务逻辑执行完成，开始调用del释放锁，这时释放的是index3的锁，导致index3的业务只执行1s就被别人释放。
     * 最终等于没锁的情况。
     */
    /*@Override
    public void testLock() {
        // 1 利用 setnx 来加锁 setnx lock athome
        // 设置一个 UUID 防止误删除
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,2, TimeUnit.SECONDS);

        // 2 如果返回 true 上锁成功
        if (lock) {

            // 获取缓存中对应 num 的值

            String value = redisTemplate.opsForValue().get("num");
            // 判断能否得到 num 的值
            if (StringUtils.isEmpty(value)) {
                return;
            }

            // 将值转成 数值型
            int num = Integer.parseInt(value);

            // 把 redis 中 num +1
            redisTemplate.opsForValue().set("num", String.valueOf(++num));

            // !!!删除锁之前判断 代码执行的 uuid 与 缓存中的 uuid 是否一致
            if (uuid.equals(redisTemplate.opsForValue().get("lock"))){
                // 3 uuid 一致 完成业务后 将锁删除!!!
                redisTemplate.delete("lock");
            }
        } else { // 若当前线程加锁了，之后的线程再进来程序，也会执行加锁的代码，但是会返回false(当前线程已经加锁了)
            try {
                // 4 等待之前的线程执行完
                Thread.sleep(1000);
                // 5 每隔1秒钟回调一次，再次尝试获取锁
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }*/


    // redis分布式锁二(出现异常)：优化之设置锁的过期时间
    // 问题描述：setnx刚好获取到锁，业务逻辑出现异常，导致锁无法释放
    /*@Override
    public void testLock() {
        // 1 利用 setnx 来加锁 setnx lock athome
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "athome",2, TimeUnit.SECONDS);

        // 2 如果返回 true 上锁成功
        if (lock) {

            // 获取缓存中对应 num 的值

            String value = redisTemplate.opsForValue().get("num");
            // 判断能否得到 num 的值
            if (StringUtils.isEmpty(value)) {
                return;
            }

            // 将值转成 数值型
            int num = Integer.parseInt(value);

            // 把 redis 中 num +1
            redisTemplate.opsForValue().set("num", String.valueOf(++num));

            // 3 完成业务后 将锁删除!!!
            redisTemplate.delete("lock");

        } else { // 若当前线程加锁了，之后的线程再进来程序，也会执行加锁的代码，但是会返回false(当前线程已经加锁了)
            try {
                // 4 等待之前的线程执行完
                Thread.sleep(1000);
                // 5 每隔1秒钟回调一次，再次尝试获取锁
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }*/

    // redis分布式锁一：利用 setnx 来加锁 setnx lock athome
    /*@Override
    public void testLock() {
        // 1 利用 setnx 来加锁 setnx lock athome
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "athome");

        // 2 如果返回 true 上锁成功
        if (lock) {

            // 获取缓存中对应 num 的值

            String value = redisTemplate.opsForValue().get("num");
            // 判断能否得到 num 的值
            if (StringUtils.isEmpty(value)) {
                return;
            }

            // 将值转成 数值型
            int num = Integer.parseInt(value);

            // 把 redis 中 num +1
            redisTemplate.opsForValue().set("num", String.valueOf(++num));

            // 3 完成业务后 将锁删除!!!
            redisTemplate.delete("lock");

        } else { // 若当前线程加锁了，之后的线程再进来程序，也会执行加锁的代码，但是会返回false(当前线程已经加锁了)
            try {
                // 4 等待之前的线程执行完
                Thread.sleep(1000);
                // 5 每隔1秒钟回调一次，再次尝试获取锁
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }*/

    // 本地锁
    /*@Override
    public synchronized void testLock() {

        // 1 获取缓存中对应 num 的值
        String value = redisTemplate.opsForValue().get("num");

        // 2 判断能否得到 num 的值
        if (StringUtils.isEmpty(value)) {
            return;
        }

        // 3 将值转成 数值型
        int num = Integer.parseInt(value);

        // 4 把 redis 中 num +1
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
    }*/
}
